package org.tp.framework.util;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tp.framework.constant.ExceptionConstant;
import org.tp.framework.exception.TPFrameworkException;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;

/***
 * 
 * 类工具
 *
 */
public class ClassUtils {

	private static final Logger logger = LoggerFactory.getLogger(ClassUtils.class);

	private static final String PATH_SPEARTOR = File.separator;

	public static Set<Class<?>> initClassSet(String packageName) {
		Set<Class<?>> classSet = getClass(packageName);
		return classSet;
	}

	@SuppressWarnings("unlikely-arg-type")
	private static Set<Class<?>> getClass(String packageName) {
		logger.info("ClassUtils getClass packageName is:{}", packageName);
		List<String> classNames = new ArrayList<String>();
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		String packagePath = packageName.replace(".", PATH_SPEARTOR);
		URL url = loader.getResource(packagePath);
		if (url != null) {
			String type = url.getProtocol();
			if (type.equals("file")) {
				classNames = getClassNameByFile(url.getPath(), null, true);
			}
			if (type.equals("jar")) {
			}
		}
		Set<Class<?>> classSet = new LinkedHashSet<Class<?>>();
		classNames.stream().forEach(className -> {
			try {
				if (classSet.contains(className)) {
					logger.error("ClassUtils getClass {} class to many", className);
					throw new TPFrameworkException(ExceptionConstant.CLASS_NAME_EXIST_ERROR_CODE,
							ExceptionConstant.CLASS_NAME_EXIST_ERROR_MSG);
				}
				Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
				classSet.add(clazz);
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		});

		return classSet;
	}

	// 获取所有类名字
	private static List<String> getClassNameByFile(String filePath, List<String> className, boolean childPackage) {
		List<String> classNames = new ArrayList<String>();
		File file = new File(filePath);
		File[] childFiles = file.listFiles();
		for (File childFile : childFiles) {
			if (childFile.isDirectory()) {
				if (childPackage) {
					classNames.addAll(getClassNameByFile(childFile.getPath(), classNames, childPackage));
				}
			} else {
				String childFilePath = childFile.getPath();
				if (childFilePath.endsWith(".class")) {
					childFilePath = childFilePath.substring(childFilePath.indexOf("classes") + 8,
							childFilePath.lastIndexOf("."));
					childFilePath = childFilePath.replace(PATH_SPEARTOR, ".");
					classNames.add(childFilePath);
				}
			}
		}
		return classNames;
	}

	/***
	 * 获取类中所有的方法信息
	 * 
	 * @param clazz
	 * @return
	 */
	public static List<Method> getClassMethods(Class<?> clazz) {
		if (clazz == null) {
			return null;
		}
		List<Method> methods = Arrays.asList(clazz.getDeclaredMethods());
		return methods;
	}

	/**
	 * 获取类中所有的字段信息
	 * 
	 * @param clazz
	 * @return
	 */
	public static List<Field> getClassFields(Class<?> clazz) {
		if (clazz == null) {
			return null;
		}
		List<Field> fields = new ArrayList<>();
		for (Field field : clazz.getDeclaredFields()) {
			fields.add(field);
		}

		Class<?> supClass = clazz.getSuperclass();

		List<Field> superFields = getClassFields(supClass);

		if (superFields == null) {
			return fields;
		}

		fields.addAll(superFields);

		return fields;

	}

	/**
	 * 获取指定类 指定方法参数名字
	 * 
	 * @param targetClazz
	 * @param targetMethodName
	 * @return
	 */
	public static List<String> getMethodParamNames(Class<?> targetClazz, String targetMethodName) {
		if (targetClazz == null) {
			return null;
		}
		List<String> paramNames = new ArrayList<>();
		ClassPool pool = ClassPool.getDefault();
		pool.insertClassPath(new ClassClassPath(targetClazz));
		try {
			CtClass cc = pool.get(targetClazz.getName());
			CtMethod cmethod = cc.getDeclaredMethod(targetMethodName);
			MethodInfo minfo = cmethod.getMethodInfo();
			CodeAttribute cattr = minfo.getCodeAttribute();
			LocalVariableAttribute attr = (LocalVariableAttribute) cattr.getAttribute(LocalVariableAttribute.tag);
			int pos = Modifier.isStatic(cmethod.getModifiers()) ? 0 : 1;
			for (int i = 0; i < cmethod.getParameterTypes().length; i++)
				paramNames.add(attr.variableName(i + pos));

		} catch (NotFoundException e) {
			e.printStackTrace();
		}
		return paramNames;
	}

	/**
	 * 
	 * 获取方法列表中参数名字和类型
	 * 
	 * @throws Exception
	 */
	public static Map<String, Object> getMethodParams(Method method) throws Exception {
		if (method == null) {
			return null;
		}
		ClassPool pool = ClassPool.getDefault();
		CtClass clz = pool.get(method.getDeclaringClass().getName());
		CtClass[] params = new CtClass[method.getParameterTypes().length];
		for (int i = 0; i < method.getParameterTypes().length; i++) {
			params[i] = pool.getCtClass(method.getParameterTypes()[i].getName());
		}
		CtMethod cm = clz.getDeclaredMethod(method.getName(), params);
		MethodInfo methodInfo = cm.getMethodInfo();
		CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
		LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
		int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
		String[] paramNames = new String[cm.getParameterTypes().length];
		Map<String, Object> resultMap = new LinkedHashMap<>();
		for (int i = 0; i < paramNames.length; i++) {
			paramNames[i] = attr.variableName(i + pos);
			resultMap.put(attr.variableName(i + pos), method.getParameterTypes()[i]);
		}
		return resultMap;
	}
}
